#include "kvdb/server/kvdbsqlite.h"

namespace kvdb {

////////////////////////////////////////////////////////////////////////////////

const char KVDB_SQL[] = "CREATE TABLE [kvdb] ("
                        "  [key] CHAR(64) NOT NULL ON CONFLICT REPLACE,"
                        "  [value] BLOB NOT NULL ON CONFLICT REPLACE, "
                        "  CONSTRAINT [] PRIMARY KEY ([key]) ON CONFLICT REPLACE);";

const char REPLACE_STMT_SQL[] = "REPLACE INTO kvdb(key, value) VALUES (?, ?);";
const char DELETE_STMT_SQL[] = "DELETE FROM kvdb where kvdb.key = ?;";
const char SELECT_STMT_SQL[] = "SELECT kvdb.value from kvdb where kvdb.key = ?";

KvdbSqlite::KvdbSqlite()
  : db_instance_(NULL),
    replace_stmt_(NULL),
    delete_stmt_(NULL),
    select_stmt_(NULL),
    is_transaction_(1) {
}

KvdbSqlite::~KvdbSqlite() {
  UinitKvdb();
}

bool KvdbSqlite::InitKvdb(const char *kvdb_path) {
  bool file_exsits = CheckFileExsits(kvdb_path);
  int res = sqlite3_open(kvdb_path, &db_instance_);
  if(res) {
    LOG(ERROR) << "Failure to open the database";
    sqlite3_close(db_instance_);
    return false;
  }
  if(!file_exsits) {
    char *sqlite_error_msg = 0;
    res = sqlite3_exec(db_instance_, KVDB_SQL, NULL, 0, &sqlite_error_msg);
    if(res != SQLITE_OK) {
      LOG(ERROR) << "Failure to create exec this command" << KVDB_SQL;
      sqlite3_free(sqlite_error_msg);
      return false;
    }
  }
  res = sqlite3_exec(db_instance_, "PRAGMA journal_mode = MEMORY;", 0,0,0);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "PRAGMA journal_mode = MEMORY;";
    return false;
  }
  res = sqlite3_exec(db_instance_, "BEGIN TRANSACTION;", 0,0,0);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Process transaction begin;";
    return false;
  }
  return InitStmt();
}

void KvdbSqlite::CheckTransaction() {
  is_transaction_ ++;
  if((is_transaction_ % MAX_TRANSACTION_SIZE) == 0) {
    int res = 0;
    res = sqlite3_exec(db_instance_, "END TRANSACTION;", 0,0,0);
    if(res != SQLITE_OK) {
      LOG(ERROR) << "Process transaction commit;";
      return;
    }
    res = sqlite3_exec(db_instance_, "BEGIN TRANSACTION;", 0,0,0);
    if(res != SQLITE_OK) {
      LOG(ERROR) << "Process transaction begin;";
      return;
    }
    is_transaction_ = 1;
  }
}

void KvdbSqlite::ForceTransaction() {
  int res = 0;
  res = sqlite3_exec(db_instance_, "commit;", 0,0,0);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Process transaction commit;";
    return;
  }
  res = sqlite3_exec(db_instance_, "begin;", 0,0,0);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Process transaction begin;";
    return;
  }
  is_transaction_ = 1;
}

bool KvdbSqlite::InitStmt() {
  int res = 0;
  res = sqlite3_prepare_v2(db_instance_,
                           REPLACE_STMT_SQL,
                           strlen(REPLACE_STMT_SQL),
                           &replace_stmt_,
                           0);
  if(res != SQLITE_OK) {
    LOG(ERROR) << REPLACE_STMT_SQL;
    return false;
  }
  res = sqlite3_prepare_v2(db_instance_,
                           DELETE_STMT_SQL,
                           strlen(DELETE_STMT_SQL),
                           &delete_stmt_,
                           0);
  if(res != SQLITE_OK) {
    LOG(ERROR) << DELETE_STMT_SQL;
    return false;
  }
  res = sqlite3_prepare_v2(db_instance_,
                           SELECT_STMT_SQL,
                           strlen(SELECT_STMT_SQL),
                           &select_stmt_,
                           0);
  if(res != SQLITE_OK) {
    LOG(ERROR) << SELECT_STMT_SQL;
    return false;
  }
  return true;
}

void KvdbSqlite::UinitKvdb() {
  int res = 0;
  res = sqlite3_finalize(replace_stmt_);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to finalize replace_stmt_";
  }
  res = sqlite3_finalize(delete_stmt_);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to finalize delete_stmt_";
  }
  res = sqlite3_finalize(select_stmt_);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to finalize remove_stmt_";
  }
  sqlite3_close(db_instance_);
}

bool KvdbSqlite::CheckFileExsits(const char *kvdb_path) {
  FILE *fp = fopen(kvdb_path, "rb");
  if(fp == NULL) {
    return false;
  }
  fclose(fp);
  return true;
}

bool KvdbSqlite::ReplaceKeyValue(const char *key, int key_size,
                                 const char *value, int value_size) {
  int res = 0;
  // CheckTransaction();
  res = sqlite3_reset(replace_stmt_);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to reset replace_stmt_*";
    return false;
  }
  res = sqlite3_bind_text(replace_stmt_, 1,
                          key,
                          key_size,
                          SQLITE_STATIC);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to call sqlite3_bind_*";
    return false;
  }
  res = sqlite3_bind_blob(replace_stmt_, 2,
                          (const void *)value,
                          value_size,
                          SQLITE_STATIC);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to call sqlite3_bind_*";
    return false;
  }
  res = sqlite3_step(replace_stmt_);
  if(res != SQLITE_DONE) {
    LOG(ERROR) << "Failure to call sqlite3_step";
    return false;
  }
  LOG(INFO) << "Replace Key " << res;
  LOG(INFO).write(key, key_size);
  return true;
}

bool KvdbSqlite::DeleteKeyValue(const char *key, int key_size) {
  int res = 0;
  CheckTransaction();
  res = sqlite3_reset(delete_stmt_);
  LOG(INFO) << "Delete Key";
  LOG(INFO).write(key, key_size);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to reset delete_stmt_*";
    return false;
  }
  res = sqlite3_bind_text(delete_stmt_, 1,
                          key,
                          key_size,
                          SQLITE_STATIC);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to call sqlite3_bind_*";
    return false;
  }
  res = sqlite3_step(delete_stmt_);
  if(res != SQLITE_DONE) {
    LOG(ERROR) << "Failure to call sqlite3_step";
    return false;
  }
  return true;
}

bool KvdbSqlite::SelectKeyValue(const char *key, int key_size,
                                std::vector<char> &buffer) {
  int res = 0;
  CheckTransaction();
  res = sqlite3_reset(select_stmt_);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to reset select_stmt_*";
    return false;
  }
  res = sqlite3_bind_text(select_stmt_, 1,
                          key,
                          key_size,
                          SQLITE_STATIC);
  if(res != SQLITE_OK) {
    LOG(ERROR) << "Failure to call sqlite3_bind_*";
    return false;
  }
  res = sqlite3_step(select_stmt_);
  if(res == SQLITE_ROW) {
    const void *data = sqlite3_column_blob(select_stmt_, 0);
    int data_size = sqlite3_column_bytes(select_stmt_, 0);
    buffer.resize(data_size + SIZE_OF_HEADER);
    memcpy(&buffer[SIZE_OF_HEADER], data, data_size);
    return true;
  }
  LOG(INFO).write(key, key_size);
  LOG(INFO) << "Failure find this key " << res;
  return false;
}
}
